﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Loki.Model;

namespace Loki.Utils
{
    public class LokiCollection<T> : ObservableCollection<T>, IBindingList, ISupportInitialize, ICancelAddNew, IDisposable, IRaiseItemChangedEvents, ILokiEnumerable<T>
    {
        #region Log

        /// <summary>
        /// Logger name ; must be redefined in derived classes
        /// </summary>
        protected virtual string LoggerName
        {
            get
            {
                return this.GetType().FullName;
            }
        }

        private static object _LoggerSync = new object();
        private ILog _Log = null;

        /// <summary>
        /// Gets the logger.
        /// </summary>
        /// <value>The logger.</value>
        protected ILog Log
        {
            get
            {
                lock (_LoggerSync)
                {
                    if (_Log == null)
                        _Log = ToolKit.Logger.GetLog(LoggerName);
                    return _Log;
                }
            }
        }

        #endregion Log

        #region ItemChanged

        private readonly object ItemChangedEventLock = new object();
        private EventHandler<ItemChangedEventArgs<T>> ItemChangedEvent;

        /// <summary>
        /// Event raised after the <see cref="Text" /> property value has changed.
        /// </summary>
        public event EventHandler<ItemChangedEventArgs<T>> ItemChanged
        {
            add
            {
                lock (ItemChangedEventLock)
                {
                    ItemChangedEvent += value;
                }
            }
            remove
            {
                lock (ItemChangedEventLock)
                {
                    ItemChangedEvent -= value;
                }
            }
        }

        /// <summary>
        /// Raises the <see cref="ItemChanged" /> event.
        /// </summary>
        /// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the
        /// event.</param>
        protected virtual void OnItemChanged(ItemChangedEventArgs<T> e)
        {
            EventHandler<ItemChangedEventArgs<T>> handler = null;

            lock (ItemChangedEventLock)
            {
                handler = ItemChangedEvent;

                if (handler == null)
                    return;
            }

            handler(this, e);
        }

        #endregion ItemChanged

        public void BeginInit()
        {
            SilentMode = true;
        }

        public void EndInit()
        {
            SilentMode = false;
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
            ResetBindings();
        }

        private Func<T> _AddingFunctor;

        public List<T> RemovedItems { get; private set; }

        public bool TrackDeleted { get; set; }

        public bool SilentMode { get; set; }

        private void Item_Changed(object sender, ItemChangedEventArgs<T> e)
        {
            if (!SilentMode)
                OnItemChanged(e);
        }

        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (!SilentMode)
            {
                base.OnCollectionChanged(e);
            }

            if (TrackDeleted && e.OldItems != null)
            {
                RemovedItems.AddRange(e.OldItems.Cast<T>());
            }
        }

        protected override void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (!SilentMode)
                base.OnPropertyChanged(e);
        }

        #region Disposable

        /// <summary>
        /// Releases all resources used by an instance of the <see cref="LokiCollection" /> class.
        /// </summary>
        /// <remarks>
        /// This method calls the virtual <see cref="Dispose(bool)" /> method, passing in
        /// <strong>true</strong>, and then suppresses finalization of the instance.
        /// </remarks>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Releases unmanaged resources before an instance of the <see cref="LokiCollection" />
        /// class is reclaimed by garbage collection.
        /// </summary>
        /// <remarks>
        /// This method releases unmanaged resources by calling the virtual
        /// <see cref="Dispose(bool)" /> method, passing in <strong>false</strong>.
        /// </remarks>
        ~LokiCollection()
        {
            Dispose(false);
        }

        /// <summary>
        /// Releases the unmanaged resources used by an instance of the
        /// <see cref="LokiCollection" /> class and optionally releases the managed resources.
        /// </summary>
        /// <param name="disposing"><strong>true</strong> to release both managed and unmanaged
        /// resources; <strong>false</strong> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
                Clear();
        }

        #endregion Disposable

        #region Sorting

        private List<T> _OriginalSort = null;

        /// <summary>
        /// Sorts the items of the collection in ascending order according to a key.
        /// </summary>
        /// <typeparam name="TKey">The type of the key returned by
        /// <paramref name="keySelector"/>.</typeparam>
        /// <param name="keySelector">A function to extract a key from an item.</param>
        public void Sort<TKey>(Func<T, TKey> keySelector)
        {
            if (_OriginalSort == null)
                _OriginalSort = this.ToList();
            InternalSort(Items.OrderBy(keySelector));
        }

        /// <summary>
        /// Sorts the items of the collection in ascending order according to a key.
        /// </summary>
        /// <typeparam name="TKey">The type of the key returned by
        /// <paramref name="keySelector"/>.</typeparam>
        /// <param name="keySelector">A function to extract a key from an item.</param>
        /// <param name="comparer">An <see cref="IComparer{T}"/> to compare keys.</param>
        public void Sort<TKey>(Func<T, TKey> keySelector, IComparer<TKey> comparer)
        {
            if (_OriginalSort == null)
                _OriginalSort = this.ToList();
            InternalSort(Items.OrderBy(keySelector, comparer));
        }

        public void CancelSort()
        {
            if (_OriginalSort != null)
                InternalSort(_OriginalSort);
            _OriginalSort = null;
        }

        /// <summary>
        /// Moves the items of the collection so that their orders are the same as those of the
        /// items provided.
        /// </summary>
        /// <param name="sortedItems">An <see cref="IEnumerable{T}"/> to provide item
        /// orders.</param>
        private void InternalSort(IEnumerable<T> sortedItems)
        {
            var sortedItemsList = sortedItems.ToList();

            BeginInit();
            foreach (var item in sortedItemsList)
            {
                Move(IndexOf(item), sortedItemsList.IndexOf(item));
            }
            EndInit();
        }

        #endregion Sorting

        #region ListChanged

        private readonly object ListChangedEventLock = new object();
        private ListChangedEventHandler ListChangedEvent;

        /// <summary>
        /// Event raised after the <see cref="Text" /> property value has changed.
        /// </summary>
        public event ListChangedEventHandler ListChanged
        {
            add
            {
                lock (ListChangedEventLock)
                {
                    ListChangedEvent += value;
                }
            }
            remove
            {
                lock (ListChangedEventLock)
                {
                    ListChangedEvent -= value;
                }
            }
        }

        /// <summary>
        /// Raises the <see cref="ListChanged" /> event.
        /// </summary>
        /// <param name="e"><see cref="ListChangedEventArgs" /> object that provides the arguments
        /// for the event.</param>
        protected virtual void OnListChanged(ListChangedEventArgs e)
        {
            ListChangedEventHandler handler = null;

            lock (ListChangedEventLock)
            {
                handler = ListChangedEvent;

                if (handler == null)
                    return;
            }

            handler(this, e);
        }

        #endregion ListChanged

        private int addNewPos = -1;
        private bool raiseItemChangedEvents = false;

        [NonSerialized()]
        private PropertyDescriptorCollection itemTypeProperties = null;

        [NonSerialized()]
        private int lastChangeIndex = -1;

        #region Constructors

        public LokiCollection(Func<T> P_AddingFunctor = null)
            : base()
        {
            _AddingFunctor = P_AddingFunctor;
            Initialize();
        }

        public LokiCollection(IList<T> list, Func<T> P_AddingFunctor = null)
            : base(list)
        {
            _AddingFunctor = P_AddingFunctor;
            Initialize();
        }

        private SynchronizationContext _SyncContext;

        private void Initialize()
        {
            TrackDeleted = true;
            SilentMode = false;
            RemovedItems = new List<T>();

            _SyncContext = SynchronizationContext.Current;

            // Check for INotifyPropertyChanged
            if (typeof(INotifyPropertyChanged).IsAssignableFrom(typeof(T)))
            {
                // Supports INotifyPropertyChanged
                this.raiseItemChangedEvents = true;

                // Loop thru the items already in the collection and hook their change notification.
                foreach (T item in this.Items)
                {
                    BindItem(item);
                }
            }
        }

        private bool ItemTypeHasDefaultConstructor
        {
            get
            {
                Type itemType = typeof(T);

                if (itemType.IsPrimitive)
                {
                    return true;
                }

                if (itemType.GetConstructor(BindingFlags.Public | BindingFlags.Instance | BindingFlags.CreateInstance, null, new Type[0], null) != null)
                {
                    return true;
                }

                return false;
            }
        }

        #endregion Constructors

        #region ListChanged event

        ///
        ///
        /// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.ResetBindings"]/*' />
        /// <devdoc> </devdoc>
        public void ResetBindings()
        {
            FireListChanged(ListChangedType.Reset, -1);
        }

        /// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.ResetItem"]/*' />
        /// <devdoc> </devdoc>
        public void ResetItem(int position)
        {
            FireListChanged(ListChangedType.ItemChanged, position);
        }

        // Private helper method
        private void FireListChanged(ListChangedType type, int index)
        {
            if (!SilentMode)
            {
                OnListChanged(new ListChangedEventArgs(type, index));
            }
        }

        #endregion ListChanged event

        #region Collection overrides

        // Collection<T> funnels all list changes through the four virtual methods below. We
        // override these so that we can commit any pending new item and fire the proper ListChanged
        // events.

        protected override void ClearItems()
        {
            EndNew(addNewPos);

            if (this.raiseItemChangedEvents)
            {
                foreach (T item in this.Items)
                {
                    UnbindItem(item);
                }
            }

            if (TrackDeleted)
            {
                RemovedItems.AddRange(this);
            }

            base.ClearItems();

            FireListChanged(ListChangedType.Reset, -1);
        }

        protected override void InsertItem(int index, T item)
        {
            EndNew(addNewPos);
            base.InsertItem(index, item);

            if (this.raiseItemChangedEvents && addNewPos != index)
            {
                BindItem(item);
            }

            FireListChanged(ListChangedType.ItemAdded, index);
        }

        protected override void RemoveItem(int index)
        {
            // Need to all RemoveItem if this on the AddNew item
            if (!(this.addNewPos >= 0 && this.addNewPos == index))
            {
                // not removing new
                EndNew(addNewPos);
                if (this.raiseItemChangedEvents)
                {
                    UnbindItem(this[index]);
                }
            }

            base.RemoveItem(index);
            FireListChanged(ListChangedType.ItemDeleted, index);
        }

        protected override void SetItem(int index, T item)
        {
            if (this.raiseItemChangedEvents)
            {
                UnbindItem(this[index]);
            }

            base.SetItem(index, item);

            if (this.raiseItemChangedEvents)
            {
                BindItem(item);
            }

            FireListChanged(ListChangedType.ItemChanged, index);
        }

        #endregion Collection overrides

        #region ICancelAddNew interface

        /// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.CancelNew"]/*' />
        /// <devdoc> If item added using AddNew() is still cancellable, then remove that item from
        /// the list. </devdoc>
        public virtual void CancelNew(int itemIndex)
        {
            if (addNewPos >= 0 && addNewPos == itemIndex)
            {
                RemoveItem(addNewPos);
                addNewPos = -1;
            }
        }

        /// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.EndNew"]/*' />
        /// <devdoc> If item added using AddNew() is still cancellable, then commit that item.
        /// </devdoc>
        public virtual void EndNew(int itemIndex)
        {
            if (addNewPos >= 0 && addNewPos == itemIndex)
            {
                if (this.raiseItemChangedEvents)
                {
                    if (this.Count > itemIndex)
                    {
                        BindItem(this[itemIndex]);
                    }
                }
                addNewPos = -1;
            }
        }

        #endregion ICancelAddNew interface

        #region IBindingList interface

        /// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.AddNew"]/*' />
        /// <devdoc> Adds a new item to the list. Calls <see cref='AddNewCore'> to create and add
        /// the item.
        ///
        /// Add operations are cancellable via the <see cref='ICancelAddNew'> interface. The
        /// position of the new item is tracked until the add operation is either cancelled by a
        /// call to <see cref='CancelNew'>, explicitly commited by a call to <see cref='EndNew'>, or
        /// implicitly commmited some other operation that changes the contents of the list (such as
        /// an Insert or Remove). When an add operation is cancelled, the new item is removed from
        /// the list. </devdoc>
        public T AddNew()
        {
            return (T)((this as IBindingList).AddNew());
        }

        object IBindingList.AddNew()
        {
            // Create new item and add it to list
            object newItem = AddNewCore();

            // Record position of new item (to support cancellation later on)
            addNewPos = (newItem != null) ? IndexOf((T)newItem) : -1;

            // Return new item to caller
            return newItem;
        }

        /// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.AddNewCore"]/*' />
        /// <devdoc> Creates a new item and adds it to the list.
        ///
        /// The base implementation raises the AddingNew event to allow an event handler to supply a
        /// custom item to add to the list. Otherwise an item of type T is created. The new item is
        /// then added to the end of the list. </devdoc>
        protected virtual object AddNewCore()
        {
            // Allow event handler to supply the new item for us
            object newItem = null;

            newItem = CreateItem();

            // Add item to end of list. Note: If event handler returned an item not of type T, the
            // cast below will trigger an InvalidCastException. This is by design.
            Add((T)newItem);

            // Return new item to caller
            return newItem;
        }

        private T CreateItem()
        {
            object newItem = null;

            if (_AddingFunctor != null)
                newItem = _AddingFunctor();

            // If event hander did not supply new item, create one ourselves
            else if (ItemTypeHasDefaultConstructor)
            {
                Type type = typeof(T);
                newItem = type.GetConstructor(Type.EmptyTypes).Invoke(null);
            }
            return (T)newItem;
        }

        /// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.AllowNew"]/*' />
        /// <devdoc> </devdoc>
        public bool AllowNew
        {
            get { return _AddingFunctor != null || this.ItemTypeHasDefaultConstructor; }
        }

        /// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.AllowEdit"]/*' />
        /// <devdoc> </devdoc>
        public bool AllowEdit
        {
            get { return true; }
        }

        /// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.AllowRemove"]/*' />
        /// <devdoc> </devdoc>
        public bool AllowRemove
        {
            get { return true; }
        }

        public bool SupportsChangeNotification
        {
            get { return true; }
        }

        public bool SupportsSearching
        {
            get { return false; }
        }

        public bool SupportsSorting
        {
            get { return false; }
        }

        public bool IsSorted
        {
            get { return false; }
        }

        PropertyDescriptor IBindingList.SortProperty
        {
            get { return SortPropertyCore; }
        }

        protected virtual PropertyDescriptor SortPropertyCore
        {
            get { return null; }
        }

        ListSortDirection IBindingList.SortDirection
        {
            get { return ListSortDirection.Ascending; }
        }

        void IBindingList.ApplySort(PropertyDescriptor prop, ListSortDirection direction)
        {
            throw new NotSupportedException();
        }

        void IBindingList.RemoveSort()
        {
            throw new NotSupportedException();
        }

        int IBindingList.Find(PropertyDescriptor prop, object key)
        {
            throw new NotSupportedException();
        }

        void IBindingList.AddIndex(PropertyDescriptor prop)
        {
            // Not supported
        }

        void IBindingList.RemoveIndex(PropertyDescriptor prop)
        {
            // Not supported
        }

        #endregion IBindingList interface

        public void AddRange(IEnumerable<T> P_Items)
        {
            bool L_OldRaiseEvents = SilentMode;
            SilentMode = true;
            foreach (T item in P_Items)
            {
                Add(item);
            }
            SilentMode = L_OldRaiseEvents;
            if (!L_OldRaiseEvents)
            {
                OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, P_Items.ToList()));
            }
        }

        #region Property Change Support

        private void BindItem(T item)
        {
            INotifyPropertyChanged inpc = (item as INotifyPropertyChanged);

            // Note: inpc may be null if item is null, so always check.
            if (null != inpc)
            {
                ToolKit.Event.RegisterChanged<LokiCollection<T>>
                    (inpc, this, (x, i, args) => x.Child_PropertyChanged(i, args));
            }
        }

        private void UnbindItem(T item)
        {
            INotifyPropertyChanged inpc = (item as INotifyPropertyChanged);

            // Note: inpc may be null if item is null, so always check.
            if (null != inpc)
            {
                ToolKit.Event.UnregisterChanged(inpc, this);
            }
        }

        private void Child_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (!SilentMode)
            {
                if (sender == null || e == null || string.IsNullOrEmpty(e.PropertyName))
                {
                    // Fire reset event (per INotifyPropertyChanged spec)
                    ResetBindings();
                }
                else
                {
                    T item;

                    try
                    {
                        item = (T)sender;
                    }
                    catch (InvalidCastException)
                    {
                        ResetBindings();
                        return;
                    }

                    // Find the position of the item. This should never be -1. If it is, somehow the
                    // item has been removed from our list without our knowledge.
                    int pos = lastChangeIndex;

                    if (pos < 0 || pos >= Count || !this[pos].Equals(item))
                    {
                        pos = this.IndexOf(item);
                        lastChangeIndex = pos;
                    }

                    if (pos == -1)
                    {
                        Log.Error("Item is no longer in our list but we are still getting change notifications.");
                        UnbindItem(item);
                        ResetBindings();
                    }
                    else
                    {
                        // Get the property descriptor
                        if (null == this.itemTypeProperties)
                        {
                            // Get Shape
                            itemTypeProperties = TypeDescriptor.GetProperties(typeof(T));
                            Debug.Assert(itemTypeProperties != null);
                        }

                        PropertyDescriptor pd = itemTypeProperties.Find(e.PropertyName, true);

                        // Create event args. If there was no matching property descriptor, we raise
                        // the list changed anyway.
                        ListChangedEventArgs args = new ListChangedEventArgs(ListChangedType.ItemChanged, pos, pd);
                        OnItemChanged(new ItemChangedEventArgs<T>(item));

                        // Fire the ItemChanged event
                        OnListChanged(args);
                    }
                }
            }
        }

        #endregion Property Change Support

        #region IRaiseItemChangedEvents interface

        /// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.RaisesItemChangedEvents"]/*' />
        /// <devdoc> Returns false to indicate that BindingList<T> does NOT raise ListChanged events
        /// of type ItemChanged as a result of property changes on individual list items unless
        /// those items support INotifyPropertyChanged </devdoc>
        bool IRaiseItemChangedEvents.RaisesItemChangedEvents
        {
            get { return this.raiseItemChangedEvents; }
        }

        #endregion IRaiseItemChangedEvents interface

        #region Synchronisation

        public void Synchronize<TSource>(
            ILokiEnumerable<TSource> P_SourceCollection,
            Action<TSource, T> P_Initializer,
            Func<TSource, T, bool> P_Finder,
            Expression<Func<T, object>> P_Sorter
            )
        {
            Synchronize<TSource>(P_SourceCollection, P_Initializer, P_Finder, P_Sorter, default(T));
        }

        public void Synchronize<TSource>(
            ILokiEnumerable<TSource> P_SourceCollection,
            Action<TSource, T> P_Initializer,
            Func<TSource, T, bool> P_Finder,
            Expression<Func<T, object>> P_Sorter,
            T P_EmptyValue)
        {
            this.TrackDeleted = false;

            LoadFrom<TSource>(P_SourceCollection, P_Initializer, P_Sorter, P_EmptyValue);
            ToolKit.Event.RegisterCollectionChanged(P_SourceCollection, this, (l, o, e) => l.Source_CollectionChanged(o, e, P_Initializer, P_Finder, P_Sorter, P_EmptyValue));
        }

        private void LoadFrom<TSource>(IEnumerable<TSource> P_SourceCollection, Action<TSource, T> P_Initializer, Expression<Func<T, object>> P_Sorter, T P_EmptyValue)
        {
            this.BeginInit();

            this.Clear();
            this.RemovedItems.Clear();

            foreach (var L_Item in P_SourceCollection)
            {
                var L_New = AddNew();
                if ((L_New as ISupportInitialize) != null)
                    ((ISupportInitialize)L_New).BeginInit();
                P_Initializer(L_Item, L_New);
                if ((L_New as ISupportInitialize) != null)
                    ((ISupportInitialize)L_New).EndInit();
                EndNew(addNewPos);
            }

            if (P_Sorter != null)
            {
                Sort(P_Sorter.Compile());
            }
            if (!Object.Equals(P_EmptyValue, default(T)))
            {
                Insert(0, P_EmptyValue);
            }

            EndInit();
        }

        private void Source_CollectionChanged<TSource>(object sender, NotifyCollectionChangedEventArgs e, Action<TSource, T> P_Initializer, Func<TSource, T, bool> P_Finder, Expression<Func<T, object>> P_Sorter, T P_EmptyValue)
        {
            SendOrPostCallback L_Synchro = s =>
                    {
                        switch (e.Action)
                        {
                            case NotifyCollectionChangedAction.Add:

                                foreach (TSource L_Added in e.NewItems)
                                {
                                    var L_New = AddNew();
                                    P_Initializer(L_Added, L_New);
                                    EndNew(addNewPos);
                                }
                                if (P_Sorter != null)
                                {
                                    SilentMode = true;
                                    Sort(P_Sorter.Compile());
                                    SilentMode = false;
                                }
                                if (!Object.Equals(P_EmptyValue, default(T)))
                                {
                                    SilentMode = true;
                                    int i = IndexOf(P_EmptyValue);
                                    MoveItem(i, 0);
                                    SilentMode = false;
                                }

                                break;

                            case NotifyCollectionChangedAction.Move:

                                // nothing
                                break;

                            case NotifyCollectionChangedAction.Remove:
                                foreach (TSource L_Removed in e.OldItems)
                                {
                                    var L_Old = this.FirstOrDefault(x => P_Finder(L_Removed, x));
                                    if (L_Old != null)
                                    {
                                        this.Remove(L_Old);
                                    }
                                }

                                break;

                            case NotifyCollectionChangedAction.Replace:
                                for (int i = 0; i < e.OldItems.Count; i++)
                                {
                                    var L_Old = this.FirstOrDefault(x => P_Finder((TSource)e.OldItems[i], x));
                                    if (L_Old != null)
                                    {
                                        int j = IndexOf(L_Old);
                                        T L_New = CreateItem();
                                        P_Initializer((TSource)e.NewItems[i], L_New);
                                        this[j] = L_New; ;

                                        if (P_Sorter != null)
                                        {
                                            SilentMode = true;
                                            Sort(P_Sorter.Compile());
                                            SilentMode = false;
                                        }
                                        if (!Object.Equals(P_EmptyValue, default(T)))
                                        {
                                            SilentMode = true;
                                            int index = IndexOf(P_EmptyValue);
                                            MoveItem(index, 0);
                                            SilentMode = false;
                                        }
                                    }
                                }
                                break;

                            case NotifyCollectionChangedAction.Reset:
                                IEnumerable<TSource> L_Source = sender as IEnumerable<TSource>;
                                if (L_Source != null)
                                    LoadFrom(L_Source, P_Initializer, P_Sorter, P_EmptyValue);
                                break;

                            default:
                                break;
                        }
                    };
            if (_SyncContext != null)
            {
                _SyncContext.Post(L_Synchro, null);
            }
            else
            {
                Task.Factory.StartNew(() => L_Synchro(null));
            }
        }

        #endregion Synchronisation
    }

    public class ItemChangedEventArgs<T> : EventArgs
    {
        public T Item { get; private set; }

        public ItemChangedEventArgs(T P_Item)
        {
            Item = P_Item;
        }
    }
}